package controllers

import java.io.File
import java.net.{URI, URLDecoder, URLEncoder}
import java.nio.file.Files

import actors._
import akka.actor.AbstractActor.Receive
import akka.actor.{Actor, ActorSystem, PoisonPill, Props}
import akka.stream.Materializer
import dao._
import javax.inject.{Inject, Singleton}
import org.apache.commons.lang3.StringUtils
import play.api.libs.json.{JsValue, Json}
import play.api.mvc.{AbstractController, ControllerComponents, WebSocket}
import tool.{FileTool, FormTool, MissionTool, ReportTool, Tool}
import utils._

import scala.concurrent.ExecutionContext.Implicits.global
import models.Tables._
import org.apache.commons.io.FileUtils
import org.joda.time.DateTime
import org.zeroturnaround.zip.ZipUtil
import play.api.libs.streams.ActorFlow
import play.api.libs.ws.WSClient
import tool.Pojo._
import tool.ReportPojo.{ReportData, ReportInfo}

import scala.concurrent.Future
import scala.util.{Failure, Success}
import scala.concurrent.duration._
import utils.Implicits._
import implicits.Implicits._

import scala.language.postfixOps

/**
 * Created by Administrator on 2019/8/8
 */
@Singleton
class MissionController @Inject()(cc: ControllerComponents, formTool: FormTool,
                                 )(implicit val system: ActorSystem,
                                   implicit val materializer: Materializer,
                                   implicit val missionDao: MissionDao,
                                   implicit val modeDao: ModeDao,
                                   implicit val userDao: UserDao,
                                   implicit val tool: Tool,
                                   implicit val ws: WSClient,
                                   implicit val keggInfoDao: KeggInfoDao,
                                   implicit val hmdbKeggInfoDao: HmdbKeggInfoDao,
                                   implicit val hmdbOtherNameKeggDao: HmdbOtherNameKeggDao,
                                   implicit val userKeggInfoDao: UserKeggInfoDao,
                                   implicit val metaboDbDao: MetaboDbDao
                                 ) extends
  AbstractController(cc) {

  implicit val dao = MyDao(keggInfoDao, hmdbKeggInfoDao, hmdbOtherNameKeggDao, userKeggInfoDao)

  val missionManageActor = system.actorOf(
    Props(new MissionManageActor())
  )
  missionManageActor ! "ask"

  def newMissionBefore = Action { implicit request =>
    val missionName = s"project_${Tool.generateMissionName}"
    val data = formTool.idOpForm.bindFromRequest().get
    Ok(views.html.user.newMission(missionName, data.id))

  }

  def reRunMissionBefore = Action { implicit request =>
    val data = formTool.missionIdOptionForm.bindFromRequest().get
    val missionName = s"${Tool.generateMissionName}_reRun"
    Ok(views.html.user.reRunMission(missionName, data.missionId))
  }

  def getAllSuccessMissionNames = Action.async { implicit request =>
    val userId = Tool.getUserId
    missionDao.selectAll(userId).map {
      x =>
        val array = x.map { mission =>
          Json.obj("text" -> mission.missionName, "id" -> mission.id)
        }
        Ok(Json.toJson(array))
    }
  }

  def getCpuNum = Action { implicit request =>
    val cpuNum = Tool.availCpu
    Ok(Json.toJson(cpuNum))
  }

  def missionNameCheck = Action.async { implicit request =>
    val data = formTool.missionNameForm.bindFromRequest.get
    val userId = Tool.getUserId
    missionDao.selectOptionByMissionName(userId, data.missionName).map { mission =>
      mission match {
        case Some(y) => Ok(Json.obj("valid" -> false))
        case None =>
          Ok(Json.obj("valid" -> true))
      }
    }
  }

  def newMission = Action.async(parse.multipartFormData) { implicit request =>
    val data = formTool.missionForm.bindFromRequest().get
    val configData = Tool.fdrDeal(formTool.configForm.bindFromRequest().get)
    val otherConfigData = FormTool.otherConfigForm.bindFromRequest().get
    val tmpDir = Tool.createTempDirectory("tmpDir")
    val myTmpDir = Tool.getDataDir(tmpDir)
    val myMessage = FileTool.fileCheck(myTmpDir, configData)
    if (myMessage.valid) {
      val userId = Tool.getUserId
      val treatSize = myTmpDir.compareFile.csvLines.drop(1).size
      val cpu = math.min(treatSize, configData.threadNum)
      val row = MissionRow(0, s"${data.missionName}", userId, new DateTime(), None, "preparing", cpu)
      missionDao.insert(row).flatMap(_ => missionDao.selectByMissionName(row.userId, row.missionName)).flatMap { mission =>
        val outDir = Tool.getUserMissionDir
        val missionExecutor = new MissionExecutor(mission.id, outDir)
        val (workspaceDir, resultDir) = (missionExecutor.workspaceDir, missionExecutor.resultDir)
        val dataDir = new File(workspaceDir.getParent, "data")
        FileUtils.copyDirectory(myTmpDir.tmpDir, dataDir)
        implicit val myInput = Tool.getMyInput(myTmpDir, workspaceDir, configData)
        Tool.deleteDirectory(myTmpDir.tmpDir)
        Utils.t2ConfigFile(workspaceDir, otherConfigData)
        val newMission = mission.copy(state = "wait")
        missionDao.update(newMission).map { x =>
          Ok(Json.obj("valid" -> true))
        }
      }
    } else {
      Tool.deleteDirectory(myTmpDir.tmpDir)
      Future.successful(Ok(Json.obj("valid" -> myMessage.valid, "message" -> myMessage.message)))
    }
  }

  def reRunMission = Action.async { implicit request =>
    val missionIdData = formTool.missionIdForm.bindFromRequest().get
    val data = formTool.missionForm.bindFromRequest().get
    val configData = Tool.fdrDeal(formTool.configForm.bindFromRequest().get)
    println(configData)
    val otherConfigData = FormTool.otherConfigForm.bindFromRequest().get
    val userId = Tool.getUserId
    val otherWorkspaceDir = Tool.getMissionWorkspaceDirById(missionIdData.missionId)
    val compareFile = new File(otherWorkspaceDir, "Treatment.csv")
    val treatSize = compareFile.csvLines.drop(1).size
    val cpu = math.min(treatSize, configData.threadNum)
    val row = MissionRow(0, s"${data.missionName}", userId, new DateTime(), None, "wait", cpu)
    missionDao.insert(row).flatMap(_ => missionDao.selectByMissionName(row.userId, row.missionName)).map { mission =>
      val outDir = Tool.getUserMissionDir
      val missionExecutor = new MissionExecutor(mission.id, outDir)
      val (workspaceDir, resultDir) = (missionExecutor.workspaceDir, missionExecutor.resultDir)
      val otherWorkspaceDir = Tool.getMissionWorkspaceDirById(missionIdData.missionId)
      implicit val myInput = Tool.getMyInputFromOther(otherWorkspaceDir, workspaceDir, configData)
      Utils.t2ConfigFile(workspaceDir, otherConfigData)
      Ok(Json.obj("valid" -> true))
    }
  }

  def updateMissionSocket = WebSocket.accept[JsValue, JsValue] {
    implicit request =>
      val userId = Tool.getUserId
      var beforeMissions = Utils.execFuture(missionDao.selectAll(userId))
      var currentMissions = beforeMissions
      ActorFlow.actorRef(out => Props(new Actor {
        override def receive: Receive = {
          case msg: JsValue if (msg \ "info").as[String] == "start" =>
            out ! Utils.getJsonByTs(beforeMissions)
            system.scheduler.scheduleOnce(3 seconds, self, Json.obj("info" -> "update"))
          case msg: JsValue if (msg \ "info").as[String] == "update" =>
            missionDao.selectAll(userId).map {
              missions =>
                currentMissions = missions
                if (currentMissions.size != beforeMissions.size) {
                  out ! Utils.getJsonByTs(currentMissions)
                } else {
                  val b = currentMissions.zip(beforeMissions).forall {
                    case (currentMission, beforeMission) =>
                      currentMission.id == beforeMission.id && currentMission.state == beforeMission.state
                  }
                  if (!b) {
                    out ! Utils.getJsonByTs(currentMissions)
                  }
                }
                beforeMissions = currentMissions
                system.scheduler.scheduleOnce(3 seconds, self, Json.obj("info" -> "update"))
            }
          case _ =>
            self ! PoisonPill
        }

        override def postStop(): Unit = {
          self ! PoisonPill
        }
      }))

  }

  def getAllMission = Action.async {
    implicit request =>
      val userId = Tool.getUserId
      missionDao.selectAll(userId).map {
        x =>
          Future {
            val missionIds = x.map(_.id.toString)
            val missionDir = Tool.getUserMissionDir
            missionDir.listFiles().filter {
              dir =>
                !missionIds.contains(dir.getName)
            }.foreach(Utils.deleteDirectory(_))
          }
          val array = Utils.getArrayByTs(x)
          Ok(Json.toJson(array))
      }

  }

  def getConfig = Action { implicit request =>
    val userId = Tool.getUserId
    val data = formTool.missionIdForm.bindFromRequest().get
    val workspaceDir = Tool.getMissionWorkspaceDirById(data.missionId)
    val configFile = new File(workspaceDir, "config.txt")
    val map = configFile.csvLines.map { columns =>
      (columns(0) -> columns(1))
    }.toMap
    val finalMap = Tool.correctConfigMap(map)
    val json = Json.toJsObject(finalMap)
    val confounderFile = new File(workspaceDir, Tool.confounderFileName)
    val hasCounfounder = confounderFile.exists()
    val extraDataDir = new File(workspaceDir, Tool.corExtraDataDirName)
    val hasExtraData = extraDataDir.listFiles().nonEmpty
    val otherConfigFile = Tool.getConfigFile(workspaceDir)
    val dbId = if (otherConfigFile.exists()) {
      val otherConfigJson = Utils.getJsonByConfigFile(workspaceDir)
      val otherConfigData = FormTool.otherConfigForm.bind(otherConfigJson).get
      val dbIdOp = Utils.execFuture(metaboDbDao.selectOp(otherConfigData.dbId))
      if (dbIdOp.isDefined) {
        otherConfigData.dbId.toString
      } else "0"
    } else "0"
    val newJson = Json.obj("hasCounfounder" -> hasCounfounder, "hasExtraData" -> hasExtraData,
      "dbId" -> dbId)
    val finalJson = json ++ newJson
    Ok(finalJson)

  }

  def downloadResult = Action.async {
    implicit request =>
      val userId = Tool.getUserId
      val data = formTool.missionIdForm.bindFromRequest().get
      val missionId = data.missionId
      missionDao.selectByMissionId(userId, missionId).zip(userDao.selectById(userId)).map {
        case (mission, user) =>
          val missionIdDir = Tool.getMissionIdDir(missionId)
          val resultFile = new File(missionIdDir, s"result.zip")
          val unitFile = new File(missionIdDir, "unit.txt")
          if (Tool.isTestMode) {
            ReportTool.produceZip(mission)
          }
          if (!unitFile.exists()) {
            ReportTool.produceZip(mission)
          } else {
            val isProduceHtml = if (unitFile.str == user.unit) false else true
            if (isProduceHtml) {
              val htmlFile = ReportTool.produceHtml(mission)
              val pdfFile = ReportTool.producePdf(mission)
              ZipUtil.replaceEntry(resultFile, "report.html", htmlFile)
              ZipUtil.replaceEntry(resultFile, "report.pdf", pdfFile)
            }
          }
          Ok.sendFile(resultFile).withHeaders(
            CONTENT_DISPOSITION -> Tool.getContentDisposition(s"${
              mission.missionName
            }_result.zip"),
            CONTENT_TYPE -> "application/x-download"
          )
      }
  }

  def downloadData = Action.async {
    implicit request =>
      val userId = Tool.getUserId
      val data = formTool.missionIdForm.bindFromRequest().get
      val missionId = data.missionId
      missionDao.selectByMissionId(userId, missionId).map {
        mission =>
          val missionIdDir = Tool.getMissionIdDir(missionId)
          val dataDir = new File(missionIdDir, "data")
          val dataFile = new File(missionIdDir, s"data.zip")
          if (!dataFile.exists()) ZipUtil.pack(dataDir, dataFile)
          Ok.sendFile(dataFile).withHeaders(
            CONTENT_DISPOSITION -> Tool.getContentDisposition(s"${mission.missionName}_data.zip"),
            CONTENT_TYPE -> "application/x-download"
          )
      }
  }

  def deleteMissionById = Action.async {
    implicit request =>
      val data = formTool.missionIdForm.bindFromRequest().get
      missionDao.deleteById(data.missionId).map {
        x =>
          Future {
            val workspaceDir = Tool.getMissionWorkspaceDirById(data.missionId)
            Utils.killPid(workspaceDir)
            val missionIdDir = Tool.getMissionIdDir(data.missionId)
            Utils.deleteDirectory(missionIdDir)
          }
          Redirect(routes.MissionController.getAllMission())
      }

  }

  def getLogContent = Action.async {
    implicit request =>
      val userId = Tool.getUserId
      val data = formTool.missionIdForm.bindFromRequest().get
      missionDao.selectByMissionId(userId, data.missionId).map {
        mission =>
          val missionIdDir = Tool.getMissionIdDir(data.missionId)
          val logFile = new File(missionIdDir, s"log.txt")
          val logStr = FileUtils.readFileToString(logFile, "UTF-8")
          Ok(Json.toJson(logStr))
      }

  }

  def reportView = Action.async { implicit request =>
    val path = "/assets/report/src/"
    val data = formTool.missionIdForm.bindFromRequest().get
    val missionIdDir = Tool.getMissionIdDir(data.missionId)
    implicit val reportData = ReportTool.getReportData(missionIdDir)
    val userId = Tool.getUserId
    val reportInfo = ReportInfo()
    userDao.selectById(userId).map { user =>
      val unit = user.unit
      val reportInfo = ReportTool.getReportInfo(unit)
      val resultPath = routes.MissionController.getFile(data.missionId, "").url
      Ok(views.html.user.report.html.right(path, reportInfo, resultPath))
    }
  }

  def reportEnView = Action.async { implicit request =>
    val path = "/assets/report/src/"
    val data = formTool.missionIdForm.bindFromRequest().get
    val missionIdDir = Tool.getMissionIdDir(data.missionId)
    implicit val reportData = ReportTool.getReportData(missionIdDir)
    val userId = Tool.getUserId
    val reportInfo = ReportInfo()
    userDao.selectById(userId).map { user =>
      val unit = user.unit
      val reportInfo = ReportTool.getReportInfo(unit)
      val resultPath = routes.MissionController.getFile(data.missionId, "").url
      Ok(views.html.user.reportEn.html.right(path, reportInfo, resultPath))
    }
  }

  def pdfView = Action.async { implicit request =>
    val path = "/assets/report/src/"
    val data = formTool.missionIdForm.bindFromRequest().get
    val missionIdDir = Tool.getMissionIdDir(data.missionId)
    implicit val reportData = ReportTool.getReportData(missionIdDir)
    val userId = Tool.getUserId
    val reportInfo = ReportInfo()
    userDao.selectById(userId).map { user =>
      val unit = user.unit
      val reportInfo = ReportTool.getReportInfo(unit)
      val resultPath = routes.MissionController.getFile(data.missionId, "").url
      Ok(views.html.user.report.pdf.report(path, reportInfo, resultPath))
    }

  }

  def getMissionById = Action.async { implicit request =>
    val data = FormTool.idForm.bindFromRequest().get
    missionDao.select(data.id).map { x =>
      val json = Utils.getJsonByT(x)
      Ok(json)
    }
  }

  def getFile(missionId: Int, fileName: String) = Action { implicit request =>
    val ifModifiedSinceStr = request.headers.get(IF_MODIFIED_SINCE)
    val dir = Tool.getSourceDir(missionId)
    val decodeFileName = fileName.replace("%20", " ")
    val file = new File(dir, decodeFileName)
    val lastModifiedStr = file.lastModified().toString
    //    val MimeType = "image/png"
    //    val byteArray = Files.readAllBytes(file.toPath)
    //    Assets.at(file.getAbsolutePath)
    //    routes.Assets.at("")
    //    if (ifModifiedSinceStr.isDefined && ifModifiedSinceStr.get == lastModifiedStr) {
    //      NotModified
    //    } else {
    //      Ok(byteArray).as(MimeType).withHeaders(LAST_MODIFIED -> file.lastModified().toString)
    //    }
    Ok.sendFile(file)
  }

  def updateName = Action.async { implicit request =>
    val missionName = formTool.missionNameForm.bindFromRequest().get.missionName
    val missionId = formTool.missionIdForm.bindFromRequest().get.missionId
    val userId = Tool.getUserId
    missionDao.select(missionId).flatMap { row =>
      val newRow = row.copy(missionName = missionName)
      missionDao.update(newRow).map { x =>
        Ok(Json.toJson("success"))
      }
    }
  }

}
